package esl

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/zackeus/go-boot/freeswitch"
	"gitee.com/zackeus/go-boot/freeswitch/esl/command"
	"gitee.com/zackeus/go-boot/freeswitch/esl/response"
	"gitee.com/zackeus/go-boot/tools/jsonx"
	"gitee.com/zackeus/go-boot/tools/re"
	"gitee.com/zackeus/go-zero/core/logx"
	"gitee.com/zackeus/goutil/strutil"
	"github.com/tidwall/gjson"
	"strings"
	"time"
)

const (
	all = "_all_"
)

type (
	// Leg This struct is used to specify the individual legs of a call for the originate helpers
	Leg struct {
		CallURL      string
		LegVariables map[string]string
	}
)

// String - Build the Leg string for passing to Bridge/Originate functions
func (l Leg) String() string {
	return fmt.Sprintf("%s%s", BuildVars("[%s]", l.LegVariables), l.CallURL)
}

var defaultTimeout = 3 * time.Second

// SendMsg sendmsg 执行 app
func (e *InboundEngine) SendMsg(ctx context.Context, cmd *command.SendMessage) error {
	res, err := e.core.send(ctx, cmd)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("sendmsg failed, the reason[%s]", res.GetReply()))
	}
	return nil
}

// GetGlobalVar global_getvar 获取全局变量
func (e *InboundEngine) GetGlobalVar(ctx context.Context, key string) (string, error) {
	cmd := &command.API{
		Command:    "global_getvar",
		Arguments:  key,
		Background: true,
	}
	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return "", err
	}
	return res.GetReply(), nil
}

func (e *InboundEngine) doDTMF(ctx context.Context, api string, uuid string, key string, duration ...uint64) error {
	var args string
	if duration == nil {
		args = fmt.Sprintf("%s %s", uuid, key)
	} else {
		args = fmt.Sprintf("%s %s @%v", uuid, key, duration[0])
	}

	// uuid_send_dtmf <uuid> <digits>[@<tone_duration>]
	// uuid_recv_dtmf <uuid> <digits>[@<tone_duration>]
	// digits: 按键字符 非单个字符会依次发送
	//  -1w: 发送按键1 间隔500ms
	//  -2W: 发送按键2 间隔1000ms
	//  -1w2W: 依次发送1和2 1间隔500ms 2间隔1000ms
	cmd := &command.API{
		Command:    api,
		Arguments:  args,
		Background: true,
	}
	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(res.GetReply())
	}
	return nil
}

// SendDTMF 发送 DTMF 按键
// key: 待发送的DTMF按键
// duration: 语调持续时长(ms) 默认250
func (e *InboundEngine) SendDTMF(ctx context.Context, uuid string, key string, duration ...uint64) error {
	if duration == nil {
		return e.doDTMF(ctx, "uuid_send_dtmf", uuid, key, 250)
	}
	return e.doDTMF(ctx, "uuid_send_dtmf", uuid, key, duration...)
}

// RecvDTMF 接收 DTMF 按键
// key: 待发送的DTMF按键
// duration: 语调持续时长(ms) 默认160
func (e *InboundEngine) RecvDTMF(ctx context.Context, uuid string, key string, duration ...uint64) error {
	if duration == nil {
		return e.doDTMF(ctx, "uuid_recv_dtmf", uuid, key, 160)
	}
	return e.doDTMF(ctx, "uuid_recv_dtmf", uuid, key, duration...)
}

// FindProfiles 获取当前 profiles
func (e *InboundEngine) FindProfiles(ctx context.Context) ([]*response.Profile, error) {
	profiles := make([]*response.Profile, 0)

	cmd := &command.API{
		Command:    "sofia jsonstatus",
		Background: true,
	}
	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return profiles, err
	}

	s := gjson.Parse(res.GetReply())
	s.Get("profiles").ForEach(func(key, value gjson.Result) bool {
		profile := &response.Profile{Name: key.String()}
		if err := jsonx.UnmarshalFromString(value.Get("status").String(), profile); err != nil {
			logx.Alert(fmt.Sprintf("sip profile status unmarshal err."))
			return false
		}
		if err := jsonx.UnmarshalFromString(value.Get("info").String(), profile); err != nil {
			logx.Alert(fmt.Sprintf("sip profile info unmarshal err."))
			return false
		}
		profiles = append(profiles, profile)
		return true
	})
	return profiles, nil
}

// ReloadXml reloadxml 刷新配置
func (e *InboundEngine) ReloadXml(ctx context.Context) error {
	cmd := &command.API{
		Command:    "reloadxml",
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("reloadxml failed, the reason[%s]", res.GetReply()))
	}
	return nil
}

// ReloadAcl reloadacl 刷新ACL
func (e *InboundEngine) ReloadAcl(ctx context.Context) error {
	cmd := &command.API{
		Command:    "reloadacl",
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("reloadacl failed, the reason[%s]", res.GetReply()))
	}
	return nil
}

// StartProfile  启动 sip profile(会载入其底下的网关)
func (e *InboundEngine) StartProfile(ctx context.Context, name string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("sofia profile %s start", name),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}

	reply := res.GetReply()
	/* 去除换行符等，获取真正执行结果 */
	reply = strings.Replace(reply, response.ResReloadOk, "", -1)
	reply = strings.Replace(reply, "\n", "", -1)
	if fmt.Sprintf("%s started successfully", name) != reply {
		return errors.New(fmt.Sprintf("sip profile start filed, the reason[%s]", reply))
	}
	return nil
}

// RescanProfile  刷新 sip profile(会忽略已存在相同名称的网关)
func (e *InboundEngine) RescanProfile(ctx context.Context, name string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("sofia profile %s rescan", name),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}

	reply := res.GetReply()
	/* 去除换行符等，获取真正执行结果 */
	reply = strings.Replace(reply, response.ResReloadOk, "", -1)
	reply = strings.Replace(reply, "\n", "", -1)
	if "+OK scan complete" != reply {
		return errors.New(fmt.Sprintf("sip profile rescan filed, the reason[%s]", reply))
	}
	return nil
}

// StopProfile  停止 sip profile(会销毁其底下的网关)
func (e *InboundEngine) StopProfile(ctx context.Context, name string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("sofia profile %s stop", name),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}

	reply := res.GetReply()
	/* 去除换行符等，获取真正执行结果 */
	reply = strings.Replace(reply, response.ResReloadOk, "", -1)
	reply = strings.Replace(reply, "\n", "", -1)
	if fmt.Sprintf("stopping: %s", name) == reply || fmt.Sprintf("Invalid Profile [%s]", name) == reply {
		/* 关闭成功 或 sip profile 不存在 */
		return nil
	}
	return errors.New(fmt.Sprintf("sip profile stop failed, the reason[%s]", reply))
}

// StartGateway  启动网关
func (e *InboundEngine) StartGateway(ctx context.Context, profile string, gw string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("sofia profile %s startgw %s", profile, gw),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}

	reply := res.GetReply()
	/* 去除换行符等，获取真正执行结果 */
	reply = strings.Replace(reply, response.ResReloadOk, "", -1)
	reply = strings.Replace(reply, "\n", "", -1)

	if !strings.HasPrefix(reply, "+OK") {
		return errors.New(fmt.Sprintf("start gateway failed, the reason[%s]", reply))
	}
	return nil
}

// KillAllGateways  卸载指定 profile 的全部网关
func (e *InboundEngine) KillAllGateways(ctx context.Context, profile string) error {
	return e.KillGateway(ctx, profile, all)
}

// KillGateway  网关卸载
func (e *InboundEngine) KillGateway(ctx context.Context, profile string, gw string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("sofia profile %s killgw %s", profile, gw),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}

	reply := res.GetReply()
	/* 去除换行符等，获取真正执行结果 */
	reply = strings.Replace(reply, response.ResReloadOk, "", -1)
	reply = strings.Replace(reply, "\n", "", -1)

	if all == gw {
		/* 卸载全部网关 */
		if "+OK every gateway marked for deletion." == reply {
			return nil
		}
	} else {
		if "-ERR no such gateway." == reply || "+OK gateway marked for deletion." == reply {
			/* 卸载成功 或 gateway 不存在 */
			return nil
		}
	}

	return errors.New(fmt.Sprintf("kill gateway failed, the reason[%s]", reply))
}

func (e *InboundEngine) FindCallCenterQueues(ctx context.Context) ([]*response.Queue, error) {
	cmd := &command.API{
		Command:    fmt.Sprintf("callcenter_config queue list"),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return nil, err
	}

	reply := res.GetReply()
	/* 执行失败 */
	if !strings.HasSuffix(reply, "+OK\n") {
		return nil, errors.New(fmt.Sprintf("callcenter_config queue list failed, the reason[%s]", reply))
	}
	/* 去除不相关字符 */
	reply = strings.Replace(reply, "+OK\n", "", -1)

	queues := make([]*response.Queue, 0)
	/* 换行符分割 */
	lists := strings.Split(reply, "\n")
	/* 长度必须大于2(第一行为key 最后一行为换行符) */
	if len(lists) <= 2 {
		return queues, nil
	}

	keys := strings.Split(lists[0], "|")
	for _, l := range lists[1 : len(lists)-1] {
		data := strings.Split(l, "|")
		if len(keys) != len(data) {
			/* 数据长度与key列长度不匹配 忽略 */
			logx.Alert("the queue invalid data.")
			continue
		}
		/* 映射map */
		m := make(map[string]string)
		for i, key := range keys {
			m[key] = data[i]
		}

		queue := &response.Queue{}
		if err := queue.Unmarshal(m); err != nil {
			return nil, err
		}
		queues = append(queues, queue)
	}

	return queues, nil
}

func (e *InboundEngine) reloadCallCenterQueue(ctx context.Context, action string, name string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("callcenter_config queue %s", action),
		Arguments:  name,
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("callcenter queue %s failed, the reason[%s]", action, res.GetReply()))
	}
	return nil
}

// LoadCallCenterQueue  callcenter queue 加载
func (e *InboundEngine) LoadCallCenterQueue(ctx context.Context, name string) error {
	return e.reloadCallCenterQueue(ctx, "load", name)
}

// ReLoadCallCenterQueue  callcenter queue 重载
func (e *InboundEngine) ReLoadCallCenterQueue(ctx context.Context, name string) error {
	return e.reloadCallCenterQueue(ctx, "reload", name)
}

// UnLoadCallCenterQueue  callcenter queue 卸载
func (e *InboundEngine) UnLoadCallCenterQueue(ctx context.Context, name string) error {
	return e.reloadCallCenterQueue(ctx, "unload", name)
}

// SetCallCenterAgentStatus 设置坐席状态
func (e *InboundEngine) SetCallCenterAgentStatus(ctx context.Context, agentNo string, status string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("callcenter_config agent set status '%s' '%s'", agentNo, status),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("callcenter agent status %s set failed, the reason[%s]", status, res.GetReply()))
	}
	return nil
}

// KillChannel 关闭会话
func (e *InboundEngine) KillChannel(ctx context.Context, uuid string, cause ...string) error {
	/* 挂断原因 */
	c := string(freeswitch.HangupNormalClearing)
	if cause != nil && strutil.IsNotBlank(cause[0]) {
		c = cause[0]
	}

	cmd := &command.API{
		Command:    fmt.Sprintf("uuid_kill %s %s", uuid, c),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	reply := res.GetReply()

	if "-ERR No such channel!\n" == reply {
		/* uuid 不存在 */
		logx.Alert(fmt.Sprintf("uuid [%s] kill failed, the reason[%s]", uuid, reply))
		return nil
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("uuid [%s] kill failed, the reason[%s]", uuid, res.GetReply()))
	}
	return nil
}

// OriginateCall - 调用 FreeSWITCH 中的 originate 函数
// aLeg, bLeg Leg The aLeg and bLeg of the call respectively
// vars: channel variables to be passed to originate for both legs, contained in {}
func (e *InboundEngine) OriginateCall(ctx context.Context, aLeg, bLeg Leg, vars map[string]string) error {
	if vars == nil {
		vars = make(map[string]string)
	}

	cmd := &command.API{
		Command:    "originate",
		Arguments:  fmt.Sprintf("%s%s %s", BuildVars("{%s}", vars), aLeg.String(), bLeg.String()),
		Background: true,
	}

	res, err := e.core.send(ctx, cmd)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("originate failed, the reason[%s]", res.GetReply()))
	}
	return nil
}

// OriginateCallSync - 调用 FreeSWITCH 中的 originate 函数, 并等待回执
// aLeg, bLeg Leg The aLeg and bLeg of the call respectively
// vars: channel variables to be passed to originate for both legs, contained in {}
func (e *InboundEngine) OriginateCallSync(ctx context.Context, aLeg, bLeg Leg, vars map[string]string, timeout time.Duration) (string, error) {
	if vars == nil {
		vars = make(map[string]string)
	}

	cmd := &command.API{
		Command:    "originate",
		Arguments:  fmt.Sprintf("%s%s %s", BuildVars("{%s}", vars), aLeg.String(), bLeg.String()),
		Background: true,
	}

	// -ERR NO_ANSWER
	// -ERR CALL_REJECTED
	// +OK 889d9222-f950-454b-8611-0e19821723aa
	res, err := e.sendBgApiSync(ctx, cmd, timeout)
	if err != nil {
		return "", err
	}
	if !res.IsOk() {
		return "", errors.New(fmt.Sprintf("originate failed, the reason[%s]", res.GetReply()))
	}

	uuid := re.UUID(res.GetReply())
	if len(uuid) <= 0 {
		return "", errors.New("originate success, but unable to resolve uuid")
	}
	return uuid[0], nil
}

func (e *InboundEngine) Transfer(ctx context.Context, uuid string, bLeg Leg) error {
	/* uuid_transfer <uuid> [-bleg|-both] <dest-exten> [<dialplan>] [<context>] */
	cmd := &command.API{
		Command:    fmt.Sprintf("uuid_transfer %s -bleg", uuid),
		Arguments:  bLeg.String(),
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("uuid_transfer failed, the reason[%s]", res.GetReply()))
	}

	return nil
}

// Hold 呼叫保持
func (e *InboundEngine) Hold(ctx context.Context, uuid string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("uuid_hold"),
		Arguments:  uuid,
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("uuid_hold failed, the reason[%s]", res.GetReply()))
	}
	return nil
}

// UnHold 取消呼叫保持
func (e *InboundEngine) UnHold(ctx context.Context, uuid string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("uuid_hold off"),
		Arguments:  uuid,
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("uuid_hold off failed, the reason[%s]", res.GetReply()))
	}
	return nil
}

// Logout 注销端点
// sofia profile <name> flush_inbound_reg [<call_id> | <[user]@domain>]
func (e *InboundEngine) Logout(ctx context.Context, profile string, user string) error {
	cmd := &command.API{
		Command:    fmt.Sprintf("sofia profile %s flush_inbound_reg", profile),
		Arguments:  user,
		Background: true,
	}

	res, err := e.sendBgApiSync(ctx, cmd, defaultTimeout)
	if err != nil {
		return err
	}
	if !res.IsOk() {
		return errors.New(fmt.Sprintf("flush_inbound_reg failed, the reason[%s]", res.GetReply()))
	}
	return nil
}
